package kms

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/wuzfei/go-helper/slices"
	"github.com/zeebo/errs"
	"go.uber.org/zap"
	"io"
	"net/http"
	"strings"
	"time"
)

var Error = errs.Class("Kms")

// get请求名单
var methodGetList = make([]string, 0)

type Config struct {
	BaseUrl            string        `help:"kms接口地址" devDefault:"https://47.106.76.249:9000" default:"https://47.106.76.249:9000"`
	AppId              string        `help:"kms appId" devDefault:"f12ce38f-069b-4cbd-9ba1-ee4846200fea" default:"f12ce38f-069b-4cbd-9ba1-ee4846200fea"`
	AppKey             string        `help:"kms appKey" devDefault:"wevKrmDsxu9v9CEFevNbRDxjYP2AP3ig" default:"wevKrmDsxu9v9CEFevNbRDxjYP2AP3ig"`
	Timeout            time.Duration `help:"kms接口超时" default:"30s" releaseDefault:"10s"`
	InsecureSkipVerify bool          `help:"kms接口不进行证书校验" default:"true"`
}

type Client struct {
	baseUrl            string
	appId              string
	appKey             string
	timeout            time.Duration
	insecureSkipVerify bool

	httpClient *http.Client
	log        *zap.Logger
}

func NewClient(conf *Config) *Client {
	return &Client{
		baseUrl:            strings.TrimSuffix(conf.BaseUrl, "/"),
		appId:              conf.AppId,
		appKey:             conf.AppKey,
		timeout:            conf.Timeout,
		insecureSkipVerify: conf.InsecureSkipVerify,

		httpClient: &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{
					InsecureSkipVerify: conf.InsecureSkipVerify,
				},
			},
			Timeout: conf.Timeout,
		},
	}
}

func (c *Client) Request(req Request, res Response) error {
	raw, err := c.request(req)
	if err != nil {
		return Error.Wrap(err)
	}
	err = json.Unmarshal(raw, &res)
	res.setRaw(raw)
	return Error.Wrap(err)
}

func (c *Client) RequestRaw(req Request) (res []byte, err error) {
	res, err = c.request(req)
	if err != nil {
		err = Error.Wrap(err)
	}
	return
}

func (c *Client) request(req Request) (body []byte, err error) {
	if slices.Contains(methodGetList, req.action()) {
		body, err = c.get(req)
	} else {
		body, err = c.post(req)
	}
	return
}

func (c *Client) getApiUrl(action string) string {
	return fmt.Sprintf("%s/ehsm?Action=%s", c.baseUrl, action)
}

func (c *Client) post(req Request) (body []byte, err error) {
	input := ReqCommon{
		Payload:   req,
		Appid:     c.appId,
		Timestamp: fmt.Sprintf("%d", time.Now().UnixMilli()),
	}

	params, err := json.Marshal(input)
	if err != nil {
		return
	}
	_signParams := make(map[string]any)
	err = json.Unmarshal(params, &_signParams)
	if err != nil {
		return
	}
	_signParams["sign"] = c.signature(_signParams)
	_body, err := json.Marshal(_signParams)
	if err != nil {
		return
	}

	resp, err := c.httpClient.Post(c.getApiUrl(req.action()), "application/json", bytes.NewReader(_body))
	if err != nil {
		return
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("KMS request(POST) httpCode error:[%d]", resp.StatusCode)
	}
	return io.ReadAll(resp.Body)
}

func (c *Client) get(req Request) (body []byte, err error) {
	resp, err := c.httpClient.Get(c.getApiUrl(req.action()))
	if err != nil {
		return
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("KMS request(GET) httpCode error:[%d]", resp.StatusCode)
	}
	return io.ReadAll(resp.Body)
}

func (c *Client) signature(input map[string]any) string {
	signStr := MapAnyToString(input)
	mac := hmac.New(sha256.New, []byte(c.appKey))
	_, _ = mac.Write([]byte(signStr))
	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
